import {
  Component,
  OnInit,
  ElementRef,
  ViewChild,
  OnChanges,
  SimpleChange,
  EventEmitter,
  OnDestroy
} from '@angular/core';
import { Action, State } from "@ngrx/store";
import { SocketService } from "socket";
import { getId, StockKData, StockDetail } from "utils";
import { CmHttp } from "CmHttp";
import { Observable } from "rxjs/Observable";
import { ReplaySubject } from "rxjs/ReplaySubject";
import { Subscription } from "rxjs/Subscription";
import * as moment from "moment";
import { BROADCAST_TYPE, BroadcastService } from "broadcast";
import { BASEURL, STOCKURL } from "shared";
import { compare} from "./../lib/tschart/src/lib/index"

import RenderKChart from "./../lib/tschart/src/k.chart.js";

interface TsChartParam {
  id: string;
  data: any[];
  period: number;
  priceMid: number;
}

export interface LineArg {
  key?: any;
  value: number;
  color?: string;
}

export interface ChartClickParams {
  data: string;
  type: string;
  args?: LineArg[]
}

export const KSOCKET_MAP = {
  Min1: "quoteMin1",
  Min5: "quoteMin5",
  Min15: "quoteMin15",
  Min30: "quoteMin30",
  Min60: "quoteMin60",
  Day1: "quoteDay1",
  Day7: "quoteDay7",
  Day30: "quoteDay30",
  Sec15: "quoteSec15",
  Sec30: "quoteSec30",
};

const handleChartClick = params => console.debug("Handle Chart Click: ", params);

const getEarliestTime: (dataList: StockKData[]) => string = dataList => (dataList && dataList[0]) ? dataList[0].time : '';

export const KEY_MAP = {
  "ArrowUp": "up",
  "ArrowDown": "down",
  "ArrowLeft": "left",
  "ArrowRight": "right"
};

export const PERIOD_DAY1 = "Day1";
export const METHOD_EXRIGHT = "exright";
export const SET_STOCK = "SET_STOCK";
export const SET_PERIOD = "SET_PERIOD";
export const SET_METHOD = "SET_METHOD";
export const SET_CONFIG = "SET_CONFIG";
export const SET_LISTS = "SET_LISTS";
export const SET_LISTS_CALLBACK = "SET_LISTS_CALLBACK";

@Component({
  // moduleId: module.id,
  selector: 'kchart',
  templateUrl: 'kchart.component.html',
  inputs: [
    "updateEventSource",
    "period",
    "lists",
    "method",
    "config",
    "isFullLoaded"
  ],
  outputs: [
    "btnClick"
  ]
})
export class KchartComponent implements OnInit, OnDestroy {
  @ViewChild("chart")
  chartContainer: HTMLElement;
  chartInstance: RenderKChart;
  chartSubscription: Subscription;
  updateEventSource: Observable<StockDetail>;
  isShowingSetting: boolean = false; // 设置框是否在显示
  currentSettingParams: ChartClickParams; // 设置框参数，Immutable，传入模态框
  isFullLoaded = true;
  period = PERIOD_DAY1;
  stock;
  lists;
  config;
  state;
  method = "";
  btnClick = new EventEmitter();
  storeSubject = new EventEmitter();
  store: Observable<{ stock; period; lists; cb; cofig; method }> = this.storeSubject.scan((state, action: Action) => {
    switch (action.type) {
      case SET_STOCK:
        return Object.assign({}, state, {
          stock: action.payload,
          period: this.period || PERIOD_DAY1,
          lists: ["MA", "VOL", "VOL(MA)"],
          config: this.config || {
            scrollBar: false, // 是否显示底部滑动条
          },
          cb: params => this.handleChartBtn(params)
        });
      case SET_PERIOD:
        return Object.assign({}, state, { period: action.payload });
      case SET_METHOD:
        return Object.assign({}, state, { method: action.payload });
      case SET_LISTS:
        return Object.assign({}, state, { lists: action.payload });
      case SET_LISTS_CALLBACK:
        return Object.assign({}, state, { cb: action.payload });
      case SET_CONFIG:
        return Object.assign({}, state, { config: action.payload });
      default: return state;
    }
  }, {
      stock: null,
      period: this.period || PERIOD_DAY1,
      lists: ["MA", "VOL", "VOL(MA)"],
      method: "re",
      config: this.config || {
        // mode: 0, // 默认模式0 九宫格模式1
        // theme: 0, // 默认白色颜色主题0
        scrollBar: false, // 是否显示底部滑动条
        // cursorInteract: false, // 是否允许光标交互
        // dragZoom: false, // 是否允许拖拽与缩放
        // settingBtn: false, // 是否显示设置按钮
        // title: false // 是否显示股票名称
      },
      cb: params => this.handleChartBtn(params)
    });

  chartId = getId();

  constructor(
    private broadcast: BroadcastService,
    private http: CmHttp,
    private elem: ElementRef,
    private socket: SocketService
  ) {
  }

  ngOnChanges(changes) {
    // console.debug("kChart On Change: ", changes);
    if (changes.period) {
      this.storeSubject.next({
        type: SET_PERIOD,
        payload: changes.period.currentValue
      });
    }
    if (changes.config) {
      // console.debug("Update Config: ", changes.config.currentValue);
      this.storeSubject.next({
        type: SET_CONFIG,
        payload: changes.config.currentValue
      });
    }

    if (!this.chartInstance || !this.stock) return;
    if (changes.method) {
      // this.method = (changes.method as SimpleChange).currentValue;
      // this.refreshChart();
      this.storeSubject.next({
        type: SET_METHOD,
        payload: (changes.method as SimpleChange).currentValue
      });
    }
    if (changes.lists) {
      this.chartInstance.updateIndicators(changes.lists.currentValue);
    }
  }

  ngOnInit() {
    console.debug("kChart On Init: ");
    let { chartId, updateEventSource, http } = this;
    // 检测是否存在周期或股票的变化，如果有，退订之前的Socket
    this.chartSubscription = this.store.distinctUntilChanged((pre, next) => {
      let flag = pre.stock.symbol !== next.stock.symbol || pre.period !== next.period;
      if (flag) {
        this.socket.emit("unsub", {
          interval: KSOCKET_MAP[pre.period],
          symbol: pre.stock.symbol
        });
      }
      return !flag;
    }).subscribe(
      (state) => {
        this.socket.emit("sub", {
          interval: KSOCKET_MAP[state.period],
          symbol: state.stock.symbol
        });
      },
      err =>
        console.error("KChartState Dist Error: ", err)
      );

    // 周期、股票、回调、配置config发生变化，拉取新的k线信息，重新绘图
    this.chartSubscription.add(this.store.
      subscribe(state => {
        // console.debug("State: ", state);
        this.state = state;
        this.showKChartLoading(this.chartId);
        // 改用缓存加强版本
        http.getKline(state.stock.symbol, state.period, state.method).subscribe(data => {
            this.stock = data;
            if (!data.kData || data.kData.length === 0) {
              console.error("kline no data");
              return false;
            }            
            // 临时优化：若数据直接从缓存加载，页面元素还没有完成初始化，则需要延后画图时机
            if (document.getElementById(this.chartId)) {
              this.generateNewChart(data);
            } else {
              let that = this;
              setTimeout(function(){
                that.generateNewChart(data);
              });
            }
          });

        /*
        Observable.zip(
          http.get(`${STOCKURL}/api/v2/sc/charts/${state.stock.symbol}/summary`, {}, {cacheAge: http.CACHE_AGE.Instant}),
          http.get(`${STOCKURL}/api/v2/sc/charts/${state.stock.symbol}/kline?period=${state.period}&right=${state.method}`),
          // http.get(`${BASEURL}/stock/kcharts/${state.stock.symbol}?period=${state.period}&startTime=${moment().format("YYYY-MM-DD")}&lastNPoints=1000000000`),
          (summary, newestData) => {
            return {
              summary: summary.data ? summary.data : {},
              kData: newestData.data ? newestData.data.sort(compare('time')) : []
            };
          })
          .switchMap(stock => {
            return this.isFullLoaded
              ? http
                .get(`https://api.integrity.com.cn/v1/stock/kcharts/${state.stock.symbol}?period=${state.period}&right=${state.method}&startTime=${getEarliestTime(stock.kData)}&lastNPoints=1000000000`)
                .map(historyData => {
                  return {
                    ...stock,
                    kData: [...historyData.sort(compare('time')), ...stock.kData]
                  }
                })
              : Observable.of({
                ...stock
              })
          })
          .take(1)
          .subscribe(stock => {
            this.stock = stock;
            this.generateNewChart(data);
          });
          */
      }, err =>
        console.error("KChartState Error: ", err)
      ));
    // 检查传入的KData是否为当前Symbol，不是的话退订之前的订阅
    // 但如果要在同一页面显示多个不同的K线，则不能应退订，适时调整
    this.socket.on("KChart", data => {
      // console.debug("KChart: ", data);
      let kData = Object.keys(data).map(key => data[key]).reduce(first => first);
      if (this.stock &&
        kData.symbol === this.stock.summary.symbol &&
        kData.interval === KSOCKET_MAP[this.period] &&
        kData.fq == this.method) {
        this.refreshChart(kData);
      }
    });

    this.showKChartLoading = function(chartId){
      // k线图切换股票loading
      let kchart = document.getElementById(chartId);
      if (!kchart) return;
      let svg = kchart.getElementsByTagName("svg");
      if (svg[0]) {
        let htmlLoading = "<image class='loading' xmlns:xlink='http://www.w3.org/1999/xlink' xlink:href='/static/images/loading.gif' x='"
        + (svg[0].getAttribute('width') / 2 - 16 )
        + "' y='"
        + (svg[0].getAttribute('height') / 2 - 16)
        + "' width='32' height='32'></image>";
        svg[0].innerHTML = htmlLoading
        setTimeout(function(){
          if (svg[0].innerHTML.indexOf("loading") > 0) {
            console.log("Connection failed! Please try again.");
          }
        }, 5000)
      }
    }

    this.chartSubscription.add(updateEventSource.filter(stock => !!stock["symbol"]).
      subscribe(stock => {
        // console.debug("Update Event For KChart: ", stock)
        this.storeSubject.next({
          type: SET_STOCK,
          payload: stock
        });
      }, err =>
        console.error("KChart Update Stock Error: ", err)
      ));
    // this.broadcast.broadcast
    //   .filter(event => event.type === BROADCAST_TYPE.RENDER_CHAERT)
    //   .subscribe(e => this.chartInstance.refreshChart(undefined));

    this.chartSubscription.add(this.broadcast
      .broadcast
      .filter(action => action.type === BROADCAST_TYPE.RENDER_CHAERT)
      .subscribe(action => this.rerenderChart()));
  }

  ngOnDestroy() {
    // console.debug("Unsub because destory: ", this.state.period, this.stock.summary.symbol);
    if (this.stock && this.stock.summary) {
      this.socket.emit("unsub", {
        interval: KSOCKET_MAP[this.period],
        symbol: this.stock.summary.symbol
      });
    }
    if (this.chartSubscription) {
      this.chartSubscription.unsubscribe()
    }
  }

  generateNewChart(stock) {
    this.chartInstance = new RenderKChart({
      param: {
        id: this.chartId, // 画布容器
        title: stock.summary["name"], // 股票名称
        period: this.state.period, // 周期
        lists: this.lists, // 指标列表
        totalShares: stock.summary["total_shares"], // 总股本
        summary: stock.summary,
        data: KchartComponent.dataTransform(stock.summary, stock.kData, this.method) // k线数据
      },
      config: this.state.config,
      cb: this.state.cb
    });
  }

  refreshChart(appendData?) {
    // console.debug("Refresh KChart: ", appendData);
    if (appendData) {
      this.stock.kData = KchartComponent.updateKData(this.stock.kData, appendData);
      // console.debug("Refresh KChart After AppendData: ", this.stock.kData.slice(-1));
    }
    // console.debug("Last KData", KchartComponent.dataTransform(this.stock.summary, this.stock.kData, this.method), KchartComponent.dataTransform(this.stock.summary, this.stock.kData, this.method).slice(-1));
    this.chartInstance.refreshChart(
      KchartComponent.dataTransform(this.stock.summary, this.stock.kData, this.method)
    );
    // console.log("refreshkchart")
  }

  handleChartBtn(e: ChartClickParams) {
    // console.debug("Chart Btn Click: ", e, e.type, this);
    switch (e.type) {
      case "setting":
        // console.debug("Chart Setting Btn Click: ", e, this);
        this.currentSettingParams = e;
        this.isShowingSetting = true;
        return;
      case "close":
        this.btnClick.next(e);
        return;
      default: return;
    }
  }

  handleSettingConfirm(param: ChartClickParams) {
    // console.debug("Handle Setting Confirm: ", param);
    this.chartInstance.updateArgs(param);
    this.isShowingSetting = false;
  }

  handleKeydown(e: KeyboardEvent) {
    // console.debug("%c Down: ", 'color: #f00' e.type, e, KEY_MAP[e.key]);
    if (!(e.key in KEY_MAP)) return;
    this.chartInstance.keyboardEmit({
      type: KEY_MAP[e.key]
    });
  }

  private static updateKData(kData, append) {
    if (!kData.slice(-1)[0]) return kData;
    if (kData.slice(-1)[0]["time"] !== append["time"]) {
      return [...kData, append];
    } else {
      return [...kData.slice(0, -1), append];
    }
  }

  rerenderChart() {
    this.chartInstance.refreshChart(undefined);
  }
  // 用来计算除权复权
  /**
  * @deprecated
  * @function {function name}
  * @param  {type} summary {description}
  * @param  {type} kData   {description}
  * @param  {type} method  {description}
  * @return {type} {description}
  */
  private static dataTransform(summary, kData, method) {
    // console.debug("KCHART METHOD DATATRANSFORM:",method);
    // for(let i = 0 ; i< kData.length; i++){
    // kData[i].open = 2;
    // kData[i].close = 6;
    // kData[i].high = 2.5;
    // kData[i].low = 2.1;
    // };
    return kData;

    // switch (method) {
    //   case METHOD_EXRIGHT:
    //     let date = summary.date_ps ? moment(summary.date_ps) : moment();
    //     console.error("Yes, Method is exright: ", kData, summary, method, date);
    //     let { allotted_ps, rationed_ps, rationed_px, bonus_ps } = summary;
    //     return kData.map(d => {
    //       if (moment(d.time).isBefore(date)) {
    //         let open = d.open - bonus_ps;
    //         open /= (1 + allotted_ps);
    //         open = (open + rationed_ps * rationed_px) / (1 + rationed_ps);
    //         let high = d.high - bonus_ps;
    //         high /= (1 + allotted_ps);
    //         high = (high + rationed_ps * rationed_px) / (1 + rationed_ps);
    //         let low = d.low - bonus_ps;
    //         low /= (1 + allotted_ps);
    //         low = (low + rationed_ps * rationed_px) / (1 + rationed_ps);
    //         let close = d.close - bonus_ps;
    //         close /= (1 + allotted_ps);
    //         close = (close + rationed_ps * rationed_px) / (1 + rationed_ps);
    //         return Object.assign({}, d, {
    //           open: +open.toFixed(2),
    //           close: +close.toFixed(2),
    //           high: +high.toFixed(2),
    //           low: +low.toFixed(2)
    //         });
    //       }
    //       return d;
    //     });
    //   default:
    //     return kData;
    // }
  }

}
